package com.fanvil.videoconference;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.SurfaceHolder;

import android.view.MotionEvent;

import java.util.ArrayList;

import com.fanvil.videoconference.FvlLogger;

public class FvlSurfaceView extends android.view.SurfaceView
        implements SurfaceHolder.Callback {
    private static FvlLogger sLogger =
            FvlLogger.getLogger(FvlSurfaceView.class.getSimpleName(), FvlLogger.VERBOSE);

    public interface Callback {
        public void onSurfaceCreated(SurfaceHolder holder);

        public void onSurfaceChanged(SurfaceHolder holder, int format, int width,
                                     int height);

        public void onSurfaceDestroyed(SurfaceHolder holder);
    }

    final ArrayList<Callback> mCallbacks
            = new ArrayList<Callback>();

    /**
     * The aspect ratio of the surface view will be equal
     * to the aspect ration of the camera preview.
     **/
    public static final int ASPECT_RATIO_PREVIEW = 0x01;

    /**
     * The surface view will fill completely fill its parent.
     */
    public static final int ASPECT_RATIO_STRETCH = 0x00;

    private Handler mHandler = null;
    private int mAspectRatioMode = ASPECT_RATIO_STRETCH;

    // Allows to force the aspect ratio of the preview
    private ViewAspectRatioMeasurer mVARM = new ViewAspectRatioMeasurer();

    private FvlSurfaceHolder mFvlSurfaceHolder;

    public FvlSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mFvlSurfaceHolder = new FvlSurfaceHolder();
        mHandler = new Handler();
        getHolder().addCallback(this);
    }

    public void addCallback(Callback callback) {
        synchronized (mCallbacks) {
            // This is a linear search, but in practice we'll
            // have only a couple callbacks, so it doesn't matter.
            if (mCallbacks.contains(callback) == false) {
                mCallbacks.add(callback);
            }
        }
    }

    public void removeCallback(Callback callback) {
        synchronized (mCallbacks) {
            mCallbacks.remove(callback);
        }
    }

    public FvlSurfaceHolder getFvlSurfaceHolder() {
        return mFvlSurfaceHolder;
    }

    public void setRole(FvlSurfaceHolder.Role role) {
        if (mFvlSurfaceHolder != null) {
            mFvlSurfaceHolder.setRole(role);
        }
    }

    public FvlSurfaceHolder.Role getRole() {
        if (mFvlSurfaceHolder != null) {
            return mFvlSurfaceHolder.getRole();
        }
        return FvlSurfaceHolder.Role.INVALID;
    }

    public FvlSurfaceHolder.State getState() {
        if (mFvlSurfaceHolder != null) {
            return mFvlSurfaceHolder.getState();
        }
        return FvlSurfaceHolder.State.INVALID;
    }

    public void setAspectRatioMode(int mode) {
        mAspectRatioMode = mode;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
         sLogger.d("surfaceChanged: Surface: " + holder.getSurface().toString() + " format: " + format + " width: " + width + " height: " + height);

        if (mFvlSurfaceHolder != null) {
            mFvlSurfaceHolder.setSurface(holder.getSurface());
            mFvlSurfaceHolder.setState(FvlSurfaceHolder.State.CHANGED);
            sLogger.d("surfaceChanged: SurfaceHolder: " + mFvlSurfaceHolder.toString());
        }

        for (Callback c : mCallbacks) {
            c.onSurfaceChanged(holder, format, width, height);
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        sLogger.d("surfaceCreated: Surface: " + holder.getSurface().toString());

        if (mFvlSurfaceHolder != null) {
            mFvlSurfaceHolder.setSurface(holder.getSurface());
            mFvlSurfaceHolder.setState(FvlSurfaceHolder.State.CREATED);
            sLogger.d("surfaceCreated: SurfaceHolder: " + mFvlSurfaceHolder.toString());
        }

        for (Callback c : mCallbacks) {
            c.onSurfaceCreated(holder);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        sLogger.d("surfaceDestroyed: Surface: " + holder.getSurface().toString());

        if (mFvlSurfaceHolder != null) {
            mFvlSurfaceHolder.setSurface(holder.getSurface());
            mFvlSurfaceHolder.setState(FvlSurfaceHolder.State.DESTORYED);
            sLogger.d("surfaceDestroyed: SurfaceHolder: " + mFvlSurfaceHolder.toString());
        }

        for (Callback c : mCallbacks) {
            c.onSurfaceDestroyed(holder);
        }
    }
/*
    @Override
    public boolean onTouchEvent(MotionEvent event){
        int action=event.getAction();

        int  x=(int) event.getX();
        int y=(int) event.getY();
        sLogger.d("onTouchEvent: x=" + x + " y=" + y);
        return true;
    }
*/
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mVARM.getAspectRatio() > 0 && mAspectRatioMode == ASPECT_RATIO_PREVIEW) {
            mVARM.measure(widthMeasureSpec, heightMeasureSpec);
            setMeasuredDimension(mVARM.getMeasuredWidth(), mVARM.getMeasuredHeight());
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * Requests a certain aspect ratio for the preview. You don't have to call this yourself,
     * the {@link VideoStream} will do it when it's needed.
     */
    public void requestAspectRatio(double aspectRatio) {
        if (mVARM.getAspectRatio() != aspectRatio) {
            mVARM.setAspectRatio(aspectRatio);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (mAspectRatioMode == ASPECT_RATIO_PREVIEW) {
                        requestLayout();
                    }
                }
            });
        }
    }

    /**
     * This class is a helper to measure views that require a specific aspect ratio.
     *
     * @author Jesper Borgstrup
     */
    public class ViewAspectRatioMeasurer {

        private double aspectRatio;

        public void setAspectRatio(double aspectRatio) {
            this.aspectRatio = aspectRatio;
        }

        public double getAspectRatio() {
            return this.aspectRatio;
        }

        /**
         * Measure with the aspect ratio given at construction.<br />
         * <br />
         * After measuring, get the width and height with the {@link #getMeasuredWidth()}
         * and {@link #getMeasuredHeight()} methods, respectively.
         *
         * @param widthMeasureSpec  The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
         * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
         */
        public void measure(int widthMeasureSpec, int heightMeasureSpec) {
            measure(widthMeasureSpec, heightMeasureSpec, this.aspectRatio);
        }

        /**
         * Measure with a specific aspect ratio<br />
         * <br />
         * After measuring, get the width and height with the {@link #getMeasuredWidth()}
         * and {@link #getMeasuredHeight()} methods, respectively.
         *
         * @param widthMeasureSpec  The width <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
         * @param heightMeasureSpec The height <tt>MeasureSpec</tt> passed in your <tt>View.onMeasure()</tt> method
         * @param aspectRatio       The aspect ratio to calculate measurements in respect to
         */
        public void measure(int widthMeasureSpec, int heightMeasureSpec, double aspectRatio) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize(heightMeasureSpec);

            if (heightMode == MeasureSpec.EXACTLY && widthMode == MeasureSpec.EXACTLY) {
                /*
                 * Possibility 1: Both width and height fixed
                 */
                measuredWidth = widthSize;
                measuredHeight = heightSize;

            } else if (heightMode == MeasureSpec.EXACTLY) {
                /*
                 * Possibility 2: Width dynamic, height fixed
                 */
                measuredWidth = (int) Math.min(widthSize, heightSize * aspectRatio);
                measuredHeight = (int) (measuredWidth / aspectRatio);

            } else if (widthMode == MeasureSpec.EXACTLY) {
                /*
                 * Possibility 3: Width fixed, height dynamic
                 */
                measuredHeight = (int) Math.min(heightSize, widthSize / aspectRatio);
                measuredWidth = (int) (measuredHeight * aspectRatio);

            } else {
                /*
                 * Possibility 4: Both width and height dynamic
                 */
                if (widthSize > heightSize * aspectRatio) {
                    measuredHeight = heightSize;
                    measuredWidth = (int) (measuredHeight * aspectRatio);
                } else {
                    measuredWidth = widthSize;
                    measuredHeight = (int) (measuredWidth / aspectRatio);
                }

            }
        }

        private Integer measuredWidth = null;

        /**
         * Get the width measured in the latest call to <tt>measure()</tt>.
         */
        public int getMeasuredWidth() {
            if (measuredWidth == null) {
                throw new IllegalStateException("You need to run measure() before trying to get measured dimensions");
            }
            return measuredWidth;
        }

        private Integer measuredHeight = null;

        /**
         * Get the height measured in the latest call to <tt>measure()</tt>.
         */
        public int getMeasuredHeight() {
            if (measuredHeight == null) {
                throw new IllegalStateException("You need to run measure() before trying to get measured dimensions");
            }
            return measuredHeight;
        }

    }

}
